package com.fjl.init;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fjl.LogUtil;
import com.fjl.mapper.AbpmMapper;
import com.fjl.pojo.po.AbpmVersionInfo;
import com.google.common.io.ByteStreams;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.engine.impl.util.io.InputStreamSource;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

@Component
public class BpmnInit implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(BpmnInit.class);
    private WatchService watchService;
    private Path watchPath;
    private Map<WatchKey, Path> watchKeys;

    @Value("${abpm.moduleHome:moduleHome}")
    String moduleHome;
    @Autowired
    AbpmMapper abpmMapper;

    // 是否开启三层解析
//    @Value("${abpm.threeLevelParsingEnabled}")
//    private boolean threeLevelParsingEnabled;

    @Override
    public void run(String... args) throws Exception {
        try {
            watchPath = Paths.get(moduleHome);
            this.watchService = FileSystems.getDefault().newWatchService();
            this.watchKeys = new HashMap<>();
            // 初始注册所有子目录
            registerAll(watchPath);

            List<Path> bpmnFilePaths;
            List<Path> processJsonFilePaths;
            // 加载process文件
            try (Stream<Path> paths = Files.walk(watchPath)) {
                bpmnFilePaths = paths.filter(path -> path.toString().endsWith(".bpmn")).collect(Collectors.toList());
            }
            // 加载.process.json文件
            try (Stream<Path> paths = Files.walk(watchPath)) {
                processJsonFilePaths = paths.filter(path -> path.toString().endsWith(".process.json")).collect(Collectors.toList());
            }
            // 部署bpmn文件到内存
            addProcessMap(bpmnFilePaths);
            // 部署动态子流程必要文件 .process.json 文件到内存
            addIndexJsonConfig(processJsonFilePaths);

            // 开启监听
            startWatching();
        } catch (NoSuchFileException e) {
                log.error("没有找到{}目录", e.getMessage());
        }
    }

    private void registerAll(Path... targetPaths) throws IOException {
        for (Path targetPath : targetPaths)
        {
            Files.walkFileTree(targetPath, new SimpleFileVisitor<Path>()
            {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException
                {
                    LogUtil.info("File monitoring： " + dir.toString());
                    dir.register(watchService, ENTRY_CREATE, ENTRY_MODIFY);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }


    private void startWatching() {
        Thread watchThread = new Thread(() -> {
            while (true) {
                try {
                    WatchKey key = watchService.take();

                    for (WatchEvent<?> event : key.pollEvents()) {
                        WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
                        Path fileName = watchEvent.context();
                        Path fullPath = ((Path) key.watchable()).resolve(fileName);
                        try (Stream<Path> paths = Files.walk(fullPath)) {
                            List<Path> bpmnFilePaths = paths.filter(path -> path.toString().endsWith(".bpmn")).collect(Collectors.toList());
                            // 部署bpmn文件到内存
                            addProcessMap(bpmnFilePaths);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    key.reset();
                } catch (InterruptedException e) {
                    log.debug("继续监听");
                }
            }
        }, "热部署");
        watchThread.setDaemon(true);
        watchThread.start();
    }

    @PreDestroy
    public void cleanUp() {
        try {
            watchService.close();
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    @SuppressWarnings("all")
    private void addProcessMap(Path filePath) throws IOException {
        BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
        BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(new InputStreamSource(Files.newInputStream(filePath)), false, false);
        Process process = bpmnModel.getMainProcess();
        process = addUserTaskVersion(process);
        AbpmGlobalInfo.processMap.remove(process.getId());
        AbpmGlobalInfo.processMap.put(process.getId(), process);
    }

    private void addIndexJsonConfig(List<Path> filePaths){
        filePaths.forEach(path -> {
            String topPath = null;
            if (path.toString().contains("moduleHome")) {
                topPath = "moduleHome";
            }else {
                topPath = "requestFileTemp";
            }
            try (InputStream inputStream = new FileInputStream(path.toFile())) {
                byte[] bytes = ByteStreams.toByteArray(inputStream);
                String jsonStr = new String(bytes, "utf8");
                JSONObject contentJsonObject = JSON.parseObject(jsonStr);
                String uri = path.toString().replace(topPath + File.separator, "");
                if (path.toString().contains(File.separator + "scene" + File.separator)) {
                    uri = uri.replace(File.separator + "scene", ":" + "scene");
                }else if (path.toString().contains(File.separator + "processes" + File.separator)) {
                    uri = uri.replace(File.separator + "processes" + File.separator, ":");
                }
                uri = uri.replace(File.separator + "index.process.json", "")
                        .replace(File.separator, "/");
                AbpmGlobalInfo.indexJsonConfig.remove(uri);
                AbpmGlobalInfo.indexJsonConfig.put(uri, contentJsonObject);
//                log.info(String.format("塞入category = %s配置：uri = %s, value = \n%s", "indexProcessConfig", uri, jsonStr));
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        });
    }

    @SuppressWarnings("all")
    private void addProcessMap(List<Path> filePaths) {
        filePaths.forEach(path -> {
            BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
            BpmnModel bpmnModel = null;
            try {
                bpmnModel = bpmnXMLConverter.convertToBpmnModel(new InputStreamSource(Files.newInputStream(path)), false, false);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            Process processLocal = bpmnModel.getMainProcess();
            // 解析整体并为UserTask节点添加版本信息
            processLocal = addUserTaskVersion(processLocal);
            String processDefKey = processLocal.getId();
            // 是否需要进行部署
            Process processRemote = AbpmGlobalInfo.processMap.get(processDefKey);
            try {
                if (readyForDeployment(processLocal, processRemote)) {
                    // 为bpmn文件通过md5生成唯一编码
                    BpmnModel bpmnModelLocal = new BpmnModel();
                    bpmnModelLocal.addProcess(processLocal);
                    String processLocalXml = bpmnModelToXmlString(bpmnModelLocal);
                    String BpmnSha256Hash = getSHA256Hash(processLocalXml);
                    String processVersion = processLocal.getId() + "-" + BpmnSha256Hash;
                    processLocal.setDocumentation(processVersion);
                    AbpmGlobalInfo.processMap.remove(processDefKey);
                    AbpmGlobalInfo.processMap.put(processDefKey, processLocal);
                    LogUtil.info(processDefKey + "部署成功！");
                    AbpmVersionInfo abpmVersionInfo = abpmMapper.selectAbpmVersionInfoByProcessVersion(processVersion);
                    // 使用java.time包获取当前的东八区时间
                    ZonedDateTime nowInShanghai = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
                    AbpmVersionInfo abpmVersionInfo1=new AbpmVersionInfo(java.sql.Date.valueOf(nowInShanghai.toLocalDate()), processDefKey, java.sql.Time.valueOf(nowInShanghai.toLocalTime()), processVersion);
                    if (path.toString().contains("二级流程")){
                        abpmVersionInfo1.setProcessType(2);
                    }else if (path.toString().contains("三级流程")){
                        abpmVersionInfo1.setProcessType(3);
                    }else {
                        abpmVersionInfo1.setProcessType(1);
                    }
                    if (abpmVersionInfo == null) {

                        abpmMapper.insertAbpmVersionInfo(abpmVersionInfo1);
                        LogUtil.info(processDefKey + "与数据库层面不一致，以进行同步更新");
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }


        });
    }

    private String getSHA256Hash(String input) {
        try {
            // 获取MD5算法的MessageDigest实例
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            // 更新摘要以包含输入字符串的字节
            md.update(input.getBytes());
            // 计算哈希值，结果是一个字节数组
            byte[] digest = md.digest();
            // 将字节数组转换为十六进制字符串
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            // 返回最终的哈希字符串
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    private boolean readyForDeployment(Process processLocal, Process processRemote) throws IOException {
        if (processRemote == null) {
            return true;
        }
        BpmnModel bpmnModelLocal = new BpmnModel();
        bpmnModelLocal.addProcess(processLocal);
        String processLocalXml = bpmnModelToXmlString(bpmnModelLocal);

        BpmnModel bpmnModelRemote = new BpmnModel();
        bpmnModelRemote.addProcess(processRemote);
        String processRemoteXml = bpmnModelToXmlString(bpmnModelRemote);
        return !processLocalXml.equals(processRemoteXml);
    }

    private static String bpmnModelToXmlString(BpmnModel bpmnModel) throws IOException {
        BpmnXMLConverter converter = new BpmnXMLConverter();
        byte[] bpmnBytes = converter.convertToXML(bpmnModel);
        InputStream inputStream = IOUtils.toInputStream(new String(bpmnBytes, StandardCharsets.UTF_8), String.valueOf(StandardCharsets.UTF_8));
        return IOUtils.toString(inputStream, String.valueOf(StandardCharsets.UTF_8));
    }

//    /**
//     * 解析路径使用三层解析生成新的ProcessDefKey
//     * @param path 文件路径
//     * @param processDefKey 旧的ProcessDefKey
//     * @return 新的ProcessDefKey
//     */
//    private String generateThreeLevelParsingProcessDefKey(String path, String processDefKey) {
//        String firstSubString = path.substring(path.indexOf("process"))
//                .replace("process" + File.separator, "");
//        String moduleName= null;
//        if (path.contains(File.separator + "scene" + File.separator)) {
//            moduleName = firstSubString.substring(0, firstSubString.indexOf(File.separator + "scene"));
//        }else if (path.contains(File.separator + "processes" + File.separator)){
//            moduleName = firstSubString.substring(0, firstSubString.indexOf(File.separator + "process"));
//        }
//        return moduleName + ":" + processDefKey;
//    }

    private Process addUserTaskVersion(Process process) {
        List<UserTask> userTasks = process.findFlowElementsOfType(UserTask.class);
        userTasks.forEach(userTask -> {
            StringBuilder taskInfo = new StringBuilder();
            // 组合 UserTask 的信息
            taskInfo.append(userTask.getId()).append("|")
                    .append(userTask.getName()).append("|")
                    .append(userTask.getAssignee());

            // 获取 formProperty
            List<FormProperty> formProperties = userTask.getFormProperties();
            HashMap<String, String> formMap = new HashMap<>();
            if (formProperties != null) {
                for (FormProperty formProperty : formProperties) {
                    String id = formProperty.getId();
                    String defaultExpression = formProperty.getDefaultExpression();
                    taskInfo.append("|").append(id).append("|").append(defaultExpression);
                    formMap.put(id, defaultExpression);
                }
            }else {
                formProperties = new ArrayList<>();
            }

            // 获取外接连线
            List<SequenceFlow> outgoingFlows = userTask.getOutgoingFlows();
            if (outgoingFlows != null) {
                for (SequenceFlow outgoingFlow : outgoingFlows) {
                    taskInfo.append("|").append(outgoingFlow.getTargetRef()).append("|");
                }
            }

            // 创建 SHA-256 哈希对象
            MessageDigest digest = null;
            try {
                digest = MessageDigest.getInstance("SHA-256");
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
            byte[] hash = digest.digest(taskInfo.toString().getBytes());

            // 将字符数组转为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            FormProperty version = new FormProperty();
            version.setId("version");
            version.setDefaultExpression(hexString.toString());
            formProperties.add(version);
            formMap.put("version",hexString.toString());
            AbpmGlobalInfo.formpropertiesMap.remove(process.getId() + "_" + userTask.getId());
            AbpmGlobalInfo.formpropertiesMap.put(process.getId() + "_" + userTask.getId(), formMap);
            LogUtil.info("表单解析成功" + process.getId() + "_" + userTask.getId());
        });
        return process;
    }
}
